Skip to content

feat(parquet): wire in additional parquet writer settings - #2887

Open
xanderbailey wants to merge 3 commits into
apache:mainfrom
xanderbailey:xb/parquet_writer_settings
Open

feat(parquet): wire in additional parquet writer settings#2887
xanderbailey wants to merge 3 commits into
apache:mainfrom
xanderbailey:xb/parquet_writer_settings

Conversation

@xanderbailey

@xanderbailey xanderbailey commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

What changes are included in this PR?

ParquetWriterBuilder::from_table_properties previously only translated the content-defined-chunking settings; every other Parquet knob silently fell through to parquet-rs (arrow-rs) defaults, which diverge from the Java implementation. This PR maps the row-group / page / dictionary sizing and compression table properties into the parquet-rs WriterProperties:

  • write.parquet.compression-codec (default zstd) and write.parquet.compression-level
  • write.parquet.row-group-size-bytes (default 128 MB)
  • write.parquet.page-size-bytes (default 1 MB)
  • write.parquet.page-row-limit (default 20000)
  • write.parquet.dict-size-bytes (default 2 MB)

How the new Iceberg defaults differ from the parquet-rs defaults that were previously in effect:

  • compression: parquet-rs defaults to UNCOMPRESSED, so files were previously written uncompressed; the Java default is zstd.
  • row group size: parquet-rs sets no byte limit and flushes row groups by row count only (~1M rows); we now apply a 128 MB byte budget. The row-count limit is left at the parquet-rs default since Iceberg has no property for it, so row groups now flush at 128 MB or ~1M rows, whichever comes first.
  • dictionary page size: parquet-rs defaults to 1 MB; the Iceberg default is 2 MB.
  • page size (1 MB) and page row limit (20000) already matched parquet-rs, so those are unchanged in practice.

Are these changes tested?

Yes

@xanderbailey
xanderbailey force-pushed the xb/parquet_writer_settings branch from abcfba9 to ca7d5ba Compare July 24, 2026 12:44
/// writer is built, not when properties are parsed.
pub const PROPERTY_PARQUET_COMPRESSION_CODEC: &str = "write.parquet.compression-codec";
/// Default Parquet compression codec (matches Iceberg's default since 1.4.0).
pub const PROPERTY_PARQUET_COMPRESSION_CODEC_DEFAULT: &str = "zstd";

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have called this out in the PR description but current default is actually uncompressed from what I can see.

This opens up a question generally as to if we should copy the Java defaults for these settings which might be more intuitive for user or keep the defaults as the arrow-rs defaults... I don't have a strong opinion here so interested to hear people's thoughts.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the matter of inconsistency, there are other places where arrow-rs has different defaults to parquet-java, like the compression level for zstd too:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aligned with Matt's "Principle of Least Astonishment" and with the outcome from the Rust sync - let's adopt Java defaults unless we have a reason to diverge.

assert_eq!(upper_bounds, HashMap::from([(0, Datum::int(i32::MAX))]));
}

// -----------------------------------------------------------------

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looked very claude so I removed

@xanderbailey xanderbailey changed the title feat(paquet): wire in additional parquet writer settings feat(parquet): wire in additional parquet writer settings Jul 24, 2026
@mbutrovich
mbutrovich self-requested a review July 24, 2026 17:15
@xanderbailey

Copy link
Copy Markdown
Contributor Author

Think I'll bring this one up as a topic for the rust community sync. It's not actually clear to me what our defaults should be here and if we should change them. I can see arguments both ways.

@mbutrovich

Copy link
Copy Markdown
Collaborator

My suggestion is to follow the Principle of Least Astonishment. Users interacting with their data lake across different Iceberg implementations might be surprised to get wildly different file size results from the de facto reference implementation (Java). I think in this case adhering to Java defaults, since it's likely something I don't expect to be codified in the spec, is reasonable.

@xanderbailey

Copy link
Copy Markdown
Contributor Author

I'm inclined to agree with this take @mbutrovich

"lz4" => Compression::LZ4,
"lz4_raw" => Compression::LZ4_RAW,
"gzip" => {
let level = match level {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please have these compression levels default to the same values as Java if a user doesn't pass us a compression level. Please reference this comment for examples - https://github.com/apache/iceberg-rust/pull/2887/changes#r3646169572

@Kurtiscwright

Copy link
Copy Markdown
Contributor

Overall looks great, thank you for finding and fixing this! I wonder if we could you use the comet testing proposal to validate the physical parquet matches between Java and Rust.

Comment on lines +54 to +58
const DEFAULT_ZSTD_COMPRESSION_LEVEL: i32 = 3;
/// Default gzip level.
const DEFAULT_GZIP_COMPRESSION_LEVEL: u32 = 6;
/// Default brotli level.
const DEFAULT_BROTLI_COMPRESSION_LEVEL: u32 = 1;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that I'm not using the compression codec struct here because ti doesn't have all parquet compression types enumerated. That struct is used for metadata / avro compression levels etc rather than data files and I didn't want to leak parquet into the core crate.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use the types in the compression module, unless we specifically want to diverge. While they're used primarily for metadata and puffin right now, I think they were written with data files in mind.

I'd extend that module to support brotli. It'll give us a common module where the conversions and errors are tested.

@xanderbailey
xanderbailey force-pushed the xb/parquet_writer_settings branch from bc85d99 to bc86d9f Compare July 31, 2026 00:26
@xanderbailey
xanderbailey force-pushed the xb/parquet_writer_settings branch from bc86d9f to da6cb46 Compare July 31, 2026 00:27

@Kurtiscwright Kurtiscwright left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks just about ready to merge. Left 2 new comments, one is a request to santa claus and the other is a code style nit-pick so nothing that I would argue should block a merge.

Compression::BROTLI(level)
}
"zstd" => {
let level = level.unwrap_or(DEFAULT_ZSTD_COMPRESSION_LEVEL);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason to move away from the match level {} pattern and the level_as_u32?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s actually just an parquet-rs api limitation, zstd accepts i32, the others require u32

use crate::writer::{CurrentFileStatus, DataFile};
use crate::{Error, ErrorKind, Result};

/// Default compression levels, pinned to parquet-java's defaults so files are

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish there was an integration test that would start failing if parquet-java ever changed their defaults.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think we have any Java tests in the repo so I’m not sure the lift of adding those is worth it for this PR, what do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's anything for this PR.

That being said, the proposed shared testing library might be interesting. I wonder if that can capture "this is what a Iceberg data file in Parquet looks like with table properties [X, Y, Z]". Then we can assert encodings, etc..

I think its just something to keep in mind for the future, at this point.

@dannycjones dannycjones left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @xanderbailey, this has been on my TODO list, great to see these wired up!

My primary concern at the moment is around adding behavior beyond Iceberg's Java implementation.

Comment on lines +193 to +195
/// Parquet compression codec name (e.g. `zstd`, `gzip`). Validated when the
/// writer is built, not when properties are parsed.
pub parquet_compression_codec: String,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated when the writer is built, not when properties are parsed.

This is a good tenet for the whole module - i.e. we push validation to access, since we may not care if we're writing ORC files (as an example).

If you end up pushing another commit, I suggest we add it to the module comment.

/// writer is built, not when properties are parsed.
pub const PROPERTY_PARQUET_COMPRESSION_CODEC: &str = "write.parquet.compression-codec";
/// Default Parquet compression codec (matches Iceberg's default since 1.4.0).
pub const PROPERTY_PARQUET_COMPRESSION_CODEC_DEFAULT: &str = "zstd";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aligned with Matt's "Principle of Least Astonishment" and with the outcome from the Rust sync - let's adopt Java defaults unless we have a reason to diverge.

use crate::writer::{CurrentFileStatus, DataFile};
use crate::{Error, ErrorKind, Result};

/// Default compression levels, pinned to parquet-java's defaults so files are

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's anything for this PR.

That being said, the proposed shared testing library might be interesting. I wonder if that can capture "this is what a Iceberg data file in Parquet looks like with table properties [X, Y, Z]". Then we can assert encodings, etc..

I think its just something to keep in mind for the future, at this point.

Comment on lines +50 to +52
/// Default compression levels, pinned to parquet-java's defaults so files are
/// comparable across implementations rather than tracking the parquet-rs
/// defaults, which may differ and could change without us noticing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be a standard comment or module comment? this is currently attached to DEFAULT_ZSTD_COMPRESSION_LEVEL.

///
/// Currently translates the content-defined-chunking keys
/// (`write.parquet.content-defined-chunking.*`); other keys fall back to
/// parquet-rs defaults.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dead comment

Comment on lines +54 to +58
const DEFAULT_ZSTD_COMPRESSION_LEVEL: i32 = 3;
/// Default gzip level.
const DEFAULT_GZIP_COMPRESSION_LEVEL: u32 = 6;
/// Default brotli level.
const DEFAULT_BROTLI_COMPRESSION_LEVEL: u32 = 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use the types in the compression module, unless we specifically want to diverge. While they're used primarily for metadata and puffin right now, I think they were written with data files in mind.

I'd extend that module to support brotli. It'll give us a common module where the conversions and errors are tested.

"snappy" => Compression::SNAPPY,
"lzo" => Compression::LZO,
"lz4" => Compression::LZ4,
"lz4_raw" => Compression::LZ4_RAW,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this come from? I don't see it in the iceberg-java repo, and it's not documented for iceberg-java: https://iceberg.apache.org/docs/latest/configuration/#write-properties

I'd rather we don't go beyond Java without intent.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for lzo.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think this is out of spec however, I would claim if a user wants to compress with a format supported by the parquet spec, this should be allowed?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not about the spec, since I don't think the writer properties are considered part of the spec broadly.

I'm just a bit defensive of adding logic beyond what Java supports, since it is broadening the feature surface we're responsible for. Especially with Parquet, where some of the changes introduced are not forwards compatible.

I'm not strongly against adding these, happy to hear thoughts from others on this.

@dannycjones

Copy link
Copy Markdown
Contributor

With this merged, I don't think we should close #2659 without a proper review into all configurations offered by the Java implementation. The scope is more than just Parquet writer properties.

Happy to scope that down, but I'd like to have an issue somewhere to track the overall compatibility with table properties respected by Java.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Iceberg table properties

5 participants